/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.dao;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import lt.marsrutai.mobile.android.dao.dto.ArrivalTimeDto;
import lt.marsrutai.mobile.android.dao.dto.BusRunDto;
import lt.marsrutai.mobile.android.dao.dto.BusStopDto;
import lt.marsrutai.mobile.android.dao.dto.BusTrackDto;
import lt.marsrutai.mobile.android.dao.dto.DaysOfWeekDto;
import lt.marsrutai.mobile.android.dao.dto.ScheduleDto;
import lt.marsrutai.mobile.android.dao.dto.ScopeInfoDto;
import lt.marsrutai.mobile.android.dao.dto.SearchResultDto;
import lt.marsrutai.mobile.android.dao.dto.StopDayTimesDto;
import lt.marsrutai.mobile.android.dao.dto.TransportDto;
import lt.marsrutai.mobile.android.dao.dto.VehiclePositionDto;
import lt.marsrutai.mobile.android.dao.exception.BackendException;
import lt.marsrutai.mobile.android.dao.exception.EmptyResponseException;
import lt.marsrutai.mobile.android.dao.utils.DtoFiller;
import lt.marsrutai.mobile.android.util.HttpGetRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class OnlineJsonDataDao implements DataDao, APIFunctionConstants {

    private DtoFiller dtoFiller;
    private DaoExceptionMapper exceptionMapper;

    public OnlineJsonDataDao() {
        dtoFiller = new DtoFiller();
        exceptionMapper = new DaoExceptionMapper();
    }

    private <T> T execute(DaoBody<T> daoBody) throws BackendException {
        try {
            return daoBody.execute();
        }
        catch (Exception ex) {
            throw exceptionMapper.map(ex);
        }
    }

    private abstract class DaoBody<T> {

        protected abstract T execute() throws JSONException, IOException;
    }

    private JSONArray downloadAsJSONArray(String uri) throws JSONException, IOException {
        String data = new HttpGetRequest(uri).download();

        if (null == data || data.length() == 0) {
            throw new EmptyResponseException(uri);
        }

        return new JSONArray(data);
    }

    private JSONObject downloadAsJSONObject(String uri) throws JSONException, IOException {
        String data = new HttpGetRequest(uri).download();

        if (null == data || data.length() == 0) {
            throw new EmptyResponseException(uri);
        }

        return new JSONObject(data);
    }

    @Override
    public List<ScopeInfoDto> getAvailableMobileScopes() throws BackendException {
        
        if (API_SCOPE_OVERRIDE == null) {
            return execute(new DaoBody<List<ScopeInfoDto>>() {
    
                @Override
                protected List<ScopeInfoDto> execute() throws JSONException, IOException {
                    JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_AVAILABLE_MOBILE_SCOPES).toString());
                    return dtoFiller.fillList(FUNCTION_GET_AVAILABLE_MOBILE_SCOPES, ScopeInfoDto.class, jsonArray);
                }
            });
        }
        else {
            List<ScopeInfoDto> scopes = new ArrayList<ScopeInfoDto>();
            scopes.add(getScopeInfo(API_SCOPE_OVERRIDE));
            return scopes;
        }
    }

    @Override
    public ScopeInfoDto getScopeInfo(final String scopeFolder) throws BackendException {
        return execute(new DaoBody<ScopeInfoDto>() {

            @Override
            protected ScopeInfoDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_SCOPE_INFO).scope(scopeFolder).toString());
                return dtoFiller.fillObject(FUNCTION_GET_SCOPE_INFO, ScopeInfoDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<String> getStopNames(final String scopeFolder) throws BackendException {
        return execute(new DaoBody<List<String>>() {

            @Override
            protected List<String> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_NAMES).scope(scopeFolder).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_NAMES, String.class, jsonArray);
            }
        });
    }

    @Override
    public List<BusStopDto> getStops(final String scopeFolder, final boolean withSchedules, final String searchQuery, final Double lat,
            final Double lng, final Double distance) throws BackendException {
        return execute(new DaoBody<List<BusStopDto>>() {

            @Override
            protected List<BusStopDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOPS).scope(scopeFolder)
                        .param(PARAM_WITH_SCHEDULES, withSchedules).param(PARAM_FILTER_NAME, searchQuery).param(PARAM_FILTER_LAT, lat).param(PARAM_FILTER_LNG, lng).param(PARAM_FILTER_DISTANCE, distance).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOPS, BusStopDto.class, jsonArray);
            }
        });
    }

    @Override
    public BusStopDto getStopById(final String scopeFolder, final String stopId) throws BackendException {
        return execute(new DaoBody<BusStopDto>() {

            @Override
            protected BusStopDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_STOP_BY_ID).scope(scopeFolder)
                        .param(PARAM_STOP_ID, stopId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_STOP_BY_ID, BusStopDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<ArrivalTimeDto> getArrivalTimes(final String scopeFolder, final String stopId, final String stopGroupName, final int maxMinutes,
            final int maxResults, final String time) throws BackendException {
        return execute(new DaoBody<List<ArrivalTimeDto>>() {

            @Override
            protected List<ArrivalTimeDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_ARRIVAL_TIMES).scope(scopeFolder)
                        .param(PARAM_STOP_FULL_NAME, stopGroupName)
                        .param(PARAM_STOP_ID, stopId)
                        .param(PARAM_MAX_MINUTES, maxMinutes)
                        .param(PARAM_MAX_RESULTS, maxResults)
                        .param(PARAM_TIME, time).toString());
                return dtoFiller.fillList(FUNCTION_GET_ARRIVAL_TIMES, ArrivalTimeDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<ScheduleDto> getSchedules(final String scopeFolder, final String transport) throws BackendException {
        return execute(new DaoBody<List<ScheduleDto>>() {

            @Override
            protected List<ScheduleDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_SCHEDULES).scope(scopeFolder)
                        .param(PARAM_TRANSPORT, transport).toString());
                return dtoFiller.fillList(FUNCTION_GET_SCHEDULES, ScheduleDto.class, jsonArray);
            }
        });
    }

    @Override
    public ScheduleDto getScheduleById(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<ScheduleDto>() {

            @Override
            protected ScheduleDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_SCHEDULE_BY_ID).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_SCHEDULE_BY_ID, ScheduleDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<BusTrackDto> getTracks(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<List<BusTrackDto>>() {

            @Override
            protected List<BusTrackDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_TRACKS).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillList(FUNCTION_GET_TRACKS, BusTrackDto.class, jsonArray);
            }
        });
    }

    @Override
    public BusTrackDto getTrackById(final String scopeFolder, final String trackId) throws BackendException {
        return execute(new DaoBody<BusTrackDto>() {

            @Override
            protected BusTrackDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_TRACK_BY_ID).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_TRACK_BY_ID, BusTrackDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<BusStopDto> getTrackStops(final String scopeFolder, final String trackId) throws BackendException {
        return execute(new DaoBody<List<BusStopDto>>() {

            @Override
            protected List<BusStopDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_TRACK_STOPS).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).toString());
                return dtoFiller.fillList(FUNCTION_GET_TRACK_STOPS, BusStopDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<DaysOfWeekDto> getDifferentRunningDays(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<List<DaysOfWeekDto>>() {

            @Override
            protected List<DaysOfWeekDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_DIFFERENT_RUNNING_DAYS).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillList(FUNCTION_GET_DIFFERENT_RUNNING_DAYS, DaysOfWeekDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<ArrivalTimeDto> getStopTimes(final String scopeFolder, final String trackId, final String stopId, final int day)
            throws BackendException {
        return execute(new DaoBody<List<ArrivalTimeDto>>() {

            @Override
            protected List<ArrivalTimeDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_TIMES).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).param(PARAM_STOP_ID, stopId).param(PARAM_DAY, day).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_TIMES, ArrivalTimeDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<StopDayTimesDto> getStopDayTimes(final String scopeFolder, final String trackId, final String stopId)
            throws BackendException {
        return execute(new DaoBody<List<StopDayTimesDto>>() {

            @Override
            protected List<StopDayTimesDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_DAY_TIMES).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).param(PARAM_STOP_ID, stopId).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_DAY_TIMES, StopDayTimesDto.class, jsonArray);
            }
        });
    }

    @Override
    public SearchResultDto findRoutes(final String scopeFolder, final String start, final String end, final String time,
            final boolean isArrival, final int maxRides, final int changeDuration, final int maxWalkDistance, final int walkSpeed,
            final String transports, final boolean withPolyline) throws BackendException {
        return execute(new DaoBody<SearchResultDto>() {

            @Override
            protected SearchResultDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_FIND_ROUTES).scope(scopeFolder)
                        .param(PARAM_START, start).param(PARAM_END, end).param(PARAM_TIME, time).param(PARAM_IS_ARRIVAL, isArrival)
                        .param(PARAM_MAX_RIDES, maxRides).param(PARAM_CHANGE_DURATION, changeDuration)
                        .param(PARAM_MAX_WALK_DISTANCE, maxWalkDistance).param(PARAM_WALK_SPEED, walkSpeed)
                        .param(PARAM_WITH_POLYLINE, withPolyline).param(PARAM_TRANSPORTS, transports).toString());
                return dtoFiller.fillObject(FUNCTION_FIND_ROUTES, SearchResultDto.class, jsonObject);
            }
        });
    }

    @Override
    public BusRunDto getRunById(final String scopeFolder, final String runId) throws BackendException {
        return execute(new DaoBody<BusRunDto>() {

            @Override
            protected BusRunDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_RUN_BY_ID).scope(scopeFolder)
                        .param(PARAM_RUN_ID, runId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_RUN_BY_ID, BusRunDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<TransportDto> getTransports(String scopeName) {
        throw new RuntimeException("Uniplmenented. Transports should be retrieved from local storage.");
    }

    @Override
    public List<VehiclePositionDto> getVehiclePositions(final String scopeFolder, final String transportId, final String scheduleName) throws BackendException {
        return execute(new DaoBody<List<VehiclePositionDto>>() {

            @Override
            protected List<VehiclePositionDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_VEHICLE_POSITIONS).scope(scopeFolder)
                        .param(PARAM_TRANSPORT, transportId).param(PARAM_SCHEDULE_NAME, scheduleName).toString());
                return dtoFiller.fillList(FUNCTION_GET_VEHICLE_POSITIONS, VehiclePositionDto.class, jsonArray);
            }
        });
    }
}
